Skip to content

Refactoring URLSession for multiple native protocols #1216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from

Conversation

saiHemak
Copy link
Contributor

The code in this PR is a part of #1122 and has been separated to reduce the size of the code change.

#968 refactored URLSession to decouple it from the HTTP implementation and allow native protocols to be implemented as URLProtocols, uniform with custom protocols. With this PR, we go a step further, and factor out common code which can be used with multiple native protocols. All of this code is moved to a new class called _NativeProtocol.

The existing HTTP implementation and the FTP Implementation [#1122] will be reusing _NativeProtocol.

@saiHemak
Copy link
Contributor Author

@pushkarnk please review

@pushkarnk pushkarnk requested a review from weissi September 14, 2017 10:09
Copy link
Contributor

@weissi weissi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand lots about this but had a quick look and left a few comments.

The code itself seems to be mostly moved from other files.

fatalError("Cant create DispatchIO channel")
cleanupHandler: {_ in })
else {
fatalError("Cant create DispatchIO channel")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will fail if the file doesn't exist, why crash?


class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
internal var easyHandle: _EasyHandle!
internal var totalDownloaded = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't see this being used anywhere, needed?

Copy link
Contributor Author

@saiHemak saiHemak Sep 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed ..

}()

class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
internal var easyHandle: _EasyHandle!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't seem to get modified, let?

Copy link
Contributor Author

@saiHemak saiHemak Sep 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

easyHandle is getting initialized during multiple init and hence the compiler complains Immutable value 'self.easyHandle' may only be initialized once

internal var easyHandle: _EasyHandle!
internal var totalDownloaded = 0
internal lazy var tempFileURL: URL = {
let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • don't we have a better/safer way to create temp files somewhere
  • is NSTemporaryDirectory() guaranteed to end in a / ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change has been incorporated into the code base through #1195 I have just refactored and pulled it to NativeProtocol as it can be used by multiple protocols like FTP and HTTP

@saiHemak
Copy link
Contributor Author

@weissi -Thanks for the review. Yes its just refactoring of URLSession code .Earlier the code was tightly coupled with HTTP Protocol.To implement the support for other protocols like FTP I moved out the common code in to Native protocol .Please refer to the #1122 for more details on how this code will be used to support FTP and HTTP protocols.

@pushkarnk
Copy link
Member

@swift-ci please test

1 similar comment
@pushkarnk
Copy link
Member

@swift-ci please test

@ianpartridge
Copy link
Contributor

I know this is an inherited problem and not caused by this PR, but the URLSession code is littered with fatalError() calls which I think would be better replaced by assert() or preconditionFailure(). What do you think @pushkarnk?

@alblue
Copy link
Contributor

alblue commented Sep 15, 2017

Yeah, after perusing #1122 I have the same concern :-) I think it's a good time to bring it up to date with coding practices.

return .proceed
}

func validateHeaderComplete(transferSate: _TransferState) -> URLResponse? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: transferSate -> transferState

}

fileprivate func notifyDelegate(aboutReceivedData data: Data) {
guard let t = self.task else { fatalError("Cannot notify") }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates t for the task but doesn't really use it. For example, the next line could be if case .taskDelegate(let delegate) = t.session.behaviour(for: t),

/// What action to take
override func completionAction(forCompletedRequest request: URLRequest, response: URLResponse) -> _CompletionAction {
// Redirect:
let httpURLResponse = response as! HTTPURLResponse
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern used elsewhere in the codebase is

guard let response = response as? HTTPURLResponse else {
    fatalError("Reponse was not HTTPURLResponse")
}

//TODO: Should keep track of the number of redirects that this
// request has gone through and err out once it's too large, i.e.
// call into `failWith(errorCode: )` with NSURLErrorHTTPTooManyRedirects
guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = self.internalState else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response and bodyDataDrain don't seem to be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its been used .

let url: URL
/// Raw headers received.
let parsedResponseHeader: _ParsedResponseHeader
/// Once the headers is complete, this will contain the response
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is -> are

let requestBodySource: _BodySource?
/// Body data received
let bodyDataDrain: _NativeProtocol._DataDrain
/// Describes what to do with received body data for this transfer:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this comment for?

/// The transfer completed.
///
/// The easy handle has been removed from the multi handle. This does
/// not (necessarily mean the task completed. A task that gets
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove (

@ianpartridge
Copy link
Contributor

I've done a first pass but feel like I only scratched the surface of this PR. There is so much that can be improved - not a criticism of @saiHemak who is working valiantly!

@saiHemak saiHemak force-pushed the urlsession-refactor branch 2 times, most recently from 70b606f to 1cae8dd Compare September 18, 2017 11:18
@pushkarnk
Copy link
Member

@ianpartridge Yes, there's a lot of cleanup needed, with the fatalError() calls and in general. However, I am not sure if we should add that here. I'd prefer that done in a different (set of) pull request(s). What do you think?

@ianpartridge
Copy link
Contributor

Yes, we should split up the work as much as possible.

@saiHemak saiHemak force-pushed the urlsession-refactor branch 3 times, most recently from ad85d11 to cf84a25 Compare September 26, 2017 07:27
@saiHemak saiHemak force-pushed the urlsession-refactor branch from cf84a25 to ddbad6d Compare October 3, 2017 08:50
@johnno1962
Copy link
Contributor

Hi, would it be possible to avoid merging this refactoring before #1198? Merging in the opposite order will involve a good deal more “conflicts”.

@saiHemak saiHemak force-pushed the urlsession-refactor branch from ddbad6d to 4dca8bb Compare October 10, 2017 05:39
@saiHemak
Copy link
Contributor Author

@ianpartridge @pushkarnk please review ..

@saiHemak saiHemak force-pushed the urlsession-refactor branch 2 times, most recently from 9661d13 to ab72c5e Compare October 12, 2017 10:44

fileprivate func notifyDelegate(aboutReceivedData data: Data) {
guard let t = self.task else {
fatalError("Cannot notify") }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The } needs to be indented right.


func fill(writeBuffer buffer: UnsafeMutableBufferPointer<Int8>) -> _EasyHandle._WriteBufferResult {
guard case .transferInProgress(let ts) = internalState else {
fatalError("Requested to fill write buffer, but transfer isn't in progress.") }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to have the } on the next line

@saiHemak
Copy link
Contributor Author

saiHemak commented Oct 31, 2017

@xwu , Thanks for your time in reviewing this .I agree that this would take considerable amount of time to review as it cannot be directly compared with old diffs. However, as explained in the description this PR is all about refactoring the existing code to support multiple native protocols like FTP,HTTP ... Hence most of the code is same and it is just a movement from one file to other file. Refactoring is needed because FTP also reuses the most of the code written for HTTP like header parsing,code used for transfer.For more details, please refer to PR #1122 - Initial implementation to support FTP re-uses the same code.

@saiHemak saiHemak force-pushed the urlsession-refactor branch from 26610ea to 5dbb4ba Compare October 31, 2017 06:23
@xwu
Copy link
Contributor

xwu commented Nov 1, 2017

@saiHemak For reviewers' benefit, can you please document explicitly, then, which code was moved from where, and what code is new? This could be done in text, but it would also be sufficient to have two commits here, one only moving existing code--with no additions whatsoever, maintaining all existing comments and formatting--then another commit with your changes to the code.

@saiHemak
Copy link
Contributor Author

saiHemak commented Nov 2, 2017

@xwu

Thanks for the inputs ..

Actually, though the code is just a movement, few required the renaming too ..
So I am looking for a way on how can I split the commits ..

Here are the details of refactoring and reason behind refactoring ..

  1. Moved lib curl specific code [libcurlHelpers.swift,EasyHandle.swift and MultiHandle.swift] from URLSession/http to URLSession/libcurl
    Reason : libcurl specific code is common and is not specific to HTTP .Hence moved out of the HTTP folder to a common licurl folder 


  2. Created a class NativeProtocol which basically holds the common implementation of native protocols like HTTP , FTP and so on .It implements the URLProtocol and EashHandleDelegate .
    Reason: Need of a common class which holds the basic functionality and can be extended by respective protocol implementations.

  3. Moved enableLibcurlDebugOutput and enableDebugOutput debug flags to NativeProtocol .These can be enabled on EasyHandle irrespective of the protocol.


  4. Moved _HTTPURLProtocol.InternalState from HTTPURLProtocol to _NativeProtocol.InternalState .Moved inits from HTTPURLProtocol to NativeProtocol
    Reason: 
Inits are initializing common variables like easyHandle,internal state and temporary file .
    
5. Modified HTTPURLProtocol to extend from _NativeProtocol and inits to just call super.init()



6. Moved easyHandle and tempFileURL instance variables to NativeProtocol


7. Moved implemented methods _HTTPURLProtocol: _EasyHandleDelegate .
Reason :

EasyHandleDelegate methods provides functionality for transfer irrespective of protocol to NativeProtocol-

fill(writeBuffer buffer: UnsafeMutableBufferPointer) ,
notifyDelegate(aboutUploadedData),
notifyDelegate(aboutReceivedData),
didReceive(data).
transferCompleted(withError error: NSError?)
createTransferState()
createTransferBodyDataDrain

However few methods like below are protocol specific hence have been overridden in respective protocol classes .

didReceive(headerData data: Data, contentLength: Int64)

Though the headers basically ends with \r\n irrespective of protocol the header format in FTP is different from HTTP.
For e.g.: Headers in FTP looks like a plain text header with status code and description unlike the key-value pair headers in HTTP

8.NativeProtocol has definition for configureEasyHandle(easyHandle) which needs concrete implementation which is protocol specific by sub-classes like FTP and HTTP.
Reason:
HTTP requires some more additional configurations like adding the headers, setting allowed protocols.

9.Renamed the HTTPBodySource to BodySource and moved out of the HTTP folder.
Reason: The code respective to data handling is same irrespective to protocol 

10. HTTPMessage._HTTPCharacters has been moved and renamed to _NativeProtocol.Delimiters
Reason: Refer to next point


  1. Moved HTTPURLProtocol. _ParsedResponseHeader from HTTPMessage.swift to NativeProtocol.swift under _NativeProtocol._ParsedResponseHeader .
    Reason: 
This implementation basically does header parsing based on \r\n delimiters.
Except the fact that empty header is received in case of HTTP to mark as Header completion and 150 status marks the open of data communication in FTP.

12. Moved and renamed HTTPURLProtocol._HTTPTransferState from TransferState.swift to _Nativeprotocol.TransferState . Moved related

Reason: Moved the code to Native Protocol as Transfer states have 1-to-1 with EasyHandle and irrespective of protocol

As the complete TransferState code has been aligned to Native protocol TransferState.swift file has been removed .

New Implementation :

NativeProtocol.validateHeaderComplete
Reason :
Should not throw error incase of simple-responseshttps://github.com//pull/1097

isCompleteHeader -> Inner method
Reason:

Refer to point 11 reason.In order to support both protocols this method has been introduced
-> which returns to true when the header is empty in case of HTTP.
-> Returns true if the header starts with 150

@alblue
Copy link
Contributor

alblue commented Nov 2, 2017

It would be good to split the commits, with each commit message outlining the points you have made above, so that anyone reviewing the changes afterwards would have the context without having to go back to a GitHub PR to try and understand the why. It would also help reviewing the changes independently and validate each one separately, including that all of the moves are done without changing code (for sanity) and to then ensure that such moves continue to be built via the PR testing mechanism, instead of just the end result.

@saiHemak saiHemak force-pushed the urlsession-refactor branch 3 times, most recently from 019eb13 to 349d2cf Compare November 10, 2017 08:39
Moved lib curl specific code [libcurlHelpers.swift,EasyHandle.swift and MultiHandle.swift] from URLSession/http to URLSession/libcurl
libcurl abstractions like EasyHandle and MultiHandle are not specific to HTTP.Hence moved out of the HTTP folder to a common licurl folder
…ectory

Added libcurl abstractions to libcurl directory
The code respective to data handling is same irrespective to protocol.So moving it under URLSession folder.
Moved under URLSession as the code related to data handling like read backed up by a file or buffer is common to all protocols.
Transfer State has 1-0-1 relationship with Easy handle and is not bound to any protocol.So moved the code to NativeProtocol and renamed to _NativeProtocol.
TransferState instead of HTTPTranserState
Created a class NativeProtocol which basically holds the common implementation of native protocols like HTTP , FTP and so on .
It implements the URLProtocol and EashHandleDelegate.Moved enableLibcurlDebugOutput and enableDebugOutput debug flags from HTTPURLProtocol to NativeProtocol as
these can be enabled on EasyHandle irrespective of the protocol.Moved _HTTPURLProtocol.InternalState from HTTPURLProtocol to _NativeProtocol.InternalState.
Moved inits from HTTPURLProtocol to NativeProtocol.Moved easyHandle and tempFileURL instance variables to NativeProtocol.EasyHandleDelegate methods provides
functionality for transfer irrespective of protocol to NativeProtocol-

fill(writeBuffer buffer: UnsafeMutableBufferPointer) ,
notifyDelegate(aboutUploadedData),
notifyDelegate(aboutReceivedData),
didReceive(data).
transferCompleted(withError error: NSError?)
createTransferState()
createTransferBodyDataDrain

However few methods like below are protocol specific hence have been overridden in respective protocol classes .

didReceive(headerData data: Data, contentLength: Int64)
Though the headers basically ends with \r\n irrespective of protocol the header format in FTP is different from HTTP.
For e.g.: Headers in FTP looks like a plain text header with status code and description unlike the key-value pair headers in HTTP.

NativeProtocol has definition for configureEasyHandle(easyHandle) which needs concrete implementation which is protocol specific by sub-classes like FTP and HTTP.
For e.g.:HTTP requires some more additional configurations like adding the headers, setting allowed protocols.

Moved HTTPURLProtocol. _ParsedResponseHeader from HTTPMessage.swift to NativeProtocol.swift under _NativeProtocol._ParsedResponseHeader .
This implementation basically does header parsing based on \r\n delimiters.
Except the fact that empty header is received in case of HTTP to mark as Header completion and 150 status marks the open of data communication in FTP

New Implementation :

NativeProtocol.validateHeaderComplete
Should not throw error incase of simple-responseshttps://github.com/swiftlang/pull/1097

isCompleteHeader -> Inner method
In order to support both protocols this method has been introduced
-> which returns to true when the header is empty in case of HTTP.
-> Returns true if the header starts with 150
Moved HTTPURLProtocol. _ParsedResponseHeader from HTTPMessage.swift to NativeProtocol.swift under _NativeProtocol._ParsedResponseHeader.
This implementation basically does header parsing based on \r\n delimiters.
Except the fact that empty header is received in case of HTTP to mark as Header completion and 150 status marks the open of data communication in FTP.
Moved methods specific to EasyHandle,common variables  and common initializers to NativeProtocol.swift.
Also overridden methods like configureEasyHandle which are specific to HTTP
@saiHemak saiHemak force-pushed the urlsession-refactor branch from 349d2cf to 976f82b Compare November 10, 2017 09:12
@saiHemak
Copy link
Contributor Author

@alblue @pushkarnk @xwu I have split the commit ,please review

@alblue
Copy link
Contributor

alblue commented Nov 10, 2017

The delete/add commits (64429de and a44a9f9) should really be one commit, not two. That way git will be able to figure out that there has been a move between files, rather than a mass-delete and mass-add. In fact, it might be better to spin that combined commit (of 64429de and a44a9f9) into its own separate review first of all, rebased against master. Once that can be verified it's not breaking any tests on its own, it can be merged - and then the body of this PR can be rebased upon it and we can review the changes subsequently.

So, I recommend:

  • cherry pick (and squash) 64429de and a44a9f9 onto master
  • create a new PR with that combined commit
  • get that reviewed and merged
  • revisit the changes on this PR with that base line

Sound good?

@saiHemak
Copy link
Contributor Author

@alblue : Sure

@xwu
Copy link
Contributor

xwu commented Nov 16, 2017

Likewise subsequent pairs of commits that move code. The point is to have git demonstrate that each such a change is only moving code, never adding or deleting anything. It is not possible to see this on inspection when one commit removes the code and another adds it back.

@alblue
Copy link
Contributor

alblue commented Nov 22, 2017

Since we're breaking this apart and filing separate changes, I'm going to close this request as is. Will be happy to look at the incremental changes (such as the one merged previously) in order to get the functionality in. Thanks for keeping up with this!

@alblue alblue closed this Nov 22, 2017
@saiHemak saiHemak deleted the urlsession-refactor branch December 22, 2017 08:10
@saiHemak saiHemak restored the urlsession-refactor branch December 22, 2017 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants